package com.dycong.common.Lamda;

import java.util.ArrayList;
import java.util.Spliterator;
import java.util.concurrent.atomic.LongAdder;
import java.util.function.Consumer;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;

/**
 * @author dycong
 * @Link https://mp.weixin.qq.com/s/W_cm46lAmQFgLP0gw0UcDw
 * @Link https://segmentfault.com/q/1010000007087438
 * todo 第一个方法tryAdvance就是顺序处理每个元素，类似Iterator，如果还有元素要处理，则返回true，否则返回false
 * 第二个方法trySplit，这就是为Spliterator专门设计的方法，区分与普通的Iterator，该方法会把当前元素划分一部分出去创建一个新的Spliterator作为返回，两个Spliterator变会并行执行，如果元素个数小到无法划分则返回null
 * 第三个方法estimateSize，该方法用于估算还剩下多少个元素需要遍历
 * 第四个方法characteristics，其实就是表示该Spliterator有哪些特性，用于可以更好控制和优化Spliterator的使用，
 * @date 2019/6/3 21:27
 */
public class ArrayListSpliterator<E> implements Spliterator<E> {
    //用于存放实体变量的list
    private final ArrayList<E> list;
    //遍历的当前位置
    private int index;
    //结束位置(不包括) 意思是当前可用的元素是[index, fence) = [index, fence-1]
    private int fence; // -1 until used; then one past last index

    private static final LongAdder LONG_ADDER = new LongAdder();

    // 构造方法
    public ArrayListSpliterator(ArrayList<E> list, int origin, int fence) {
        this.list = list;
        this.index = origin;
        this.fence = fence;
    }

    //第一次使用的时候初始化fence 返回结束位置
    private int getFence() { // initialize fence to size on first use
        int hi; // (a specialized variant appears in method forEach)
        ArrayList<E> lst;
        if ((hi = fence) < 0) {
            if ((lst = list) == null) {
                hi = fence = 0;
            } else {
                hi = fence = lst.size();
            }
        }
        return hi;
    }

    /**
     * 根据当前的Spliterator拆分出一个新的Spliterator
     * 相当于二分,
     * Note:共享同一个list,改变的只是下标
     * TODO: 2019/6/3 不切分就不会有ForkJoinPool线程并发执行  Thread[ForkJoinPool.commonPool-worker-1,5,main],
     * TODO 共享同一个list,改变的只是下标
     */
    @Override
    public ArrayListSpliterator<E> trySplit() {
        System.out.println("trySplit: lo=" + index + ",hi=" + (getFence() - index) + Thread.currentThread());

        LONG_ADDER.add(1L);
        int hi = getFence(), lo = index, mid = (lo + hi) >>> 1;
        return (lo >= mid) ? null : // divide range in half unless too small
                new ArrayListSpliterator<E>(list, lo, index = mid);
    }

    //单次遍历  下标index只加1
    @Override
    public boolean tryAdvance(Consumer<? super E> action) {
        System.out.println("tryAdvance: " + Thread.currentThread());

        if (action == null) {
            throw new NullPointerException();
        }
        int hi = getFence(), i = index;
        if (i < hi) {
            index = i + 1;
            @SuppressWarnings("unchecked") E e = (E) list.get(i);
            action.accept(e);
            return true;
        }
        return false;
    }

    //整体遍历
    @Override
    public void forEachRemaining(Consumer<? super E> action) {
        System.out.println("forEachRemaining: " + (getFence() - index) + Thread.currentThread());

        int i, hi, mc; // hoist accesses and checks from loop
        ArrayList<E> lst;
        Object[] a;
        if (action == null) {
            throw new NullPointerException();
        }
        if ((lst = list) != null && (a = lst.toArray()) != null) {
            if ((hi = fence) < 0) {
                hi = lst.size();
            }
            if ((i = index) >= 0 && (index = hi) <= a.length) {
                for (; i < hi; ++i) {
                    @SuppressWarnings("unchecked") E e = (E) a[i];
                    action.accept(e);
                }
            }
        }
    }

    //剩下还有多少元素
    @Override
    public long estimateSize() {
        System.out.println("estimateSize: " + (getFence() - index) + Thread.currentThread());
        return (long) (getFence() - index);
    }

    @Override
    public int characteristics() {
        return Spliterator.ORDERED | Spliterator.SIZED | Spliterator.SUBSIZED;
    }

    @Override
    public String toString() {
        return "[" + this.index + "," + getFence() + "]";
    }

    public static void main(String[] args) {
        ArrayList<Integer> arrayList = new ArrayList<>(1000);
        for (int i = 0; i <= 1000; i++) {
            arrayList.add(i);
        }

//        testSplit(arrayList);
//        testStream(arrayList);
        testParallelStream(arrayList);

        System.out.println(LONG_ADDER.sum());
    }

    public static void testStream(ArrayList<Integer> arrayList) {
        ArrayListSpliterator<Integer> als_1 = new ArrayListSpliterator(arrayList, 0, -1);
        Stream<Integer> stream = StreamSupport.stream(als_1, false);
        stream.forEach(o -> {
            System.out.println(o + "   " + Thread.currentThread());
        });
    }


    public static void testParallelStream(ArrayList<Integer> arrayList) {
        ArrayListSpliterator<Integer> als_1 = new ArrayListSpliterator(arrayList, 0, -1);
        Stream<Integer> stream = StreamSupport.stream(als_1, true);
        stream.parallel().forEach(o -> {
            System.out.println(o + "   " + Thread.currentThread());
        });
    }

    public static void testSplit(ArrayList<Integer> arrayList) {

        ArrayListSpliterator<Integer> als_1 = new ArrayListSpliterator(arrayList, 0, -1);
        System.out.println("als_1:" + als_1);    // [0,11]

        System.out.println("---------split-----------");
        ArrayListSpliterator als_2 = als_1.trySplit();
        System.out.println("als_1:" + als_1);    // [5,11]
        System.out.println("als_2:" + als_2);    // [0,5]

        // [0,11](als_1) ---> [0,5](als_2) + [5,11](als_1)

        System.out.println("---------split-----------");
        ArrayListSpliterator als_3 = als_1.trySplit();
        ArrayListSpliterator als_4 = als_2.trySplit();
        System.out.println("als_1:" + als_1);
        System.out.println("als_2:" + als_2);
        System.out.println("als_3:" + als_3);
        System.out.println("als_4:" + als_4);

        /**
         * [0,5](als_2)  --> [0,2](als_4)  + [2,5](als_2)
         * [5,11](als_1) --> [8,11](als_1) + [5,8](als_3)
         */

        System.out.println("---------test the address---------");
        System.out.println("(als_1.list == als_2.list) = " + (als_1.list == als_2.list));
        System.out.println("(als_2.list == als_3.list) = " + (als_2.list == als_3.list));
        System.out.println("(als_3.list == als_4.list) = " + (als_3.list == als_4.list));
    }
}
